<?php
// Created by: Alex K (wtwf.com) Thu Aug 17 07:41:07 2006
// Last Modified: Time-stamp: <Tue Mar  9 18:55:38 PST 2010>
// $Id: index.php,v 1.9 2006-12-26 06:25:26 ark Exp $

/** a way to make really short urls
 * set this page as your 404 error_document in your .htaccess or apache config
 *
 * ErrorDocument 404 /index.php
 *
 * when people goto yoursite.com/someshortname this will look up that
 * shortname in a small config file and if it finds it will redirect
 * you there, otherwise it'll just show the default page
 *
 * You can add new links with a kw= cgi param, it looks for a space in
 * the kw= param and uses the first token as the keyword and the
 * second as the url
 *
 * alternatively you can use an n and u cgi param for name and url
 *
 * I find a mozilla keyword is especially useful. I have mine set to 'w'
 * http://yourserver.com/?kw=%s
 * so when I'm at a url I want to shortcut I just put my cursor in the
 * location bar goto the beginning and insert 'w keyword ' at the
 * start and hit enter and I have an automatic keyword!
 *
 * When adding urls authentication is done to make sure you are coming from
 * a trusted ip or are a valid user.
 *
 * This page is also used via redirect to show the url just added with
 * a 'show' cgi parm this avoids showing people our secret kw cgi
 * param to avoid confusion in referrer logs
 *
 * It's all controlled by a config file, which has parameters and also
 * all the snippy url mappings in it. It's a winini format file with 3
 * sections. Make sure it's writeable by the webserver process.
 *
 * [users]
 * ; create these with a simple php -r 'echo crypt("somepassword");'
 * ; basic auth is used so don't use a realy good password.
 * ; It'll pretty much be sent in plaintext.
 * ark=qy------1Q
 *
 * [ips]
 * ; a list of permitted ip's that wont evven need a password
 * home=10.10.23.25
 *
 * [urls]
 * ; here's all the urls
 * goog="http://www.google.com/"
 *
 */

/* TODO(ark) support deep linking if wtwf.com/g points to google.com
 * wtwf.com/g/maps should point to google.com/maps
 */

/** Set this to be the location of your config file.  It must be
 * writable by the webserver. Since it's best not to let people get
 * your passwords (even crypted) it'd be swell if this wasn't under
 * your webserver's document root. */
define("CONFIGFILE", "/home/ark/lib/snipdb");

/** the page to load if there's nothing to be done. This page can
 * reference the variable $msg which may be set to something we want
 * the user to know.  You don't even have to have this set a very
 * plain default page will be used. */
define("FAILOVERFILE", "/home/ark/html/wtwf/default.php");

/** this page will be show if they fail to authenticate
 * it can use $msg just like FAILOVERFILE */
define("ACCESSDENIEDFILE", "/home/ark/html/wtwf/default.php");

/** The base path that should come before all snippy url keywords */
define("BASEPATH", "/");

/** The base url that should come before all snippy url keywords */
define("BASEURL", "http://{$_SERVER['HTTP_HOST']}" . BASEPATH);

/** letters that will be used to make random ids if you don't specify a name */
define("LETTERS", "bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ0123456789");

/** append a link to the config file */
function appendLink($n, $u) {
  if (!is_writeable(CONFIGFILE)) {
    doNothing("Config file is not writeable");
  }
  $fh = fopen(CONFIGFILE, 'a');
  fwrite($fh, "$n=\"$u\"\n");
  fclose($fh);
}

/** load the config file and put it in an array $settings */
function loadConfig() {
  if (!is_readable(CONFIGFILE)) {
    doNothing("Cannot load config file");
  }
  global $settings;
  if (!isset($settings)) {
    $settings = parse_ini_file(CONFIGFILE, True);
  }
  return $settings;
}

/** get a random id that doesn't exist in the settings */
function getNextID() {
  global $settings;

  $id = '';
  for ($i = 0; $i < 5; $i++) {
    $id .= substr(LETTERS, rand(0, strlen(LETTERS) - 1), 1);
  }
  if ($settings['urls'][$id]) {
    return getNextID();
  }
  return $id;
}

/** Cause the user to have to enter a username and password */
function authRequired() {
  header("WWW-Authenticate: Basic realm=\"Teeny Tiny Url\"");
  header('HTTP/1.0 401 Unauthorized');
  $msg = "Oh Burn! access DEEE-NIED!";
  if (is_readable(ACCESSDENIEDFILE)) {
    include ACCESSDENIEDFILE;
  } else {
    defaultPage($msg);
  }
  exit();
}

/** Make sure the user is coming from an approved IP or is authenticated */
function checkPermissions() {
  global $settings;
  loadConfig();

  // first let's see if the IP address is allowed
  if (isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR']) {
    foreach ($settings['ips'] as $kw => $ip) {
      if ($ip == $_SERVER['REMOTE_ADDR']) {
        return;
      }
    }
  }

  // o.k. well maybe they can authenticate by logging in
  if (!isset($_SERVER['PHP_AUTH_USER'])) {
    authRequired();
  } else {
    $username = $_SERVER['PHP_AUTH_USER'];
    $password = $_SERVER['PHP_AUTH_PW'];
  }

  if (!isset($settings['users'][$username])) {
    authRequired();
  }
  $stored = $settings['users'][$username];
  if (crypt($password, $stored) != $stored) {
    authRequired();
  }
}

function redirectToKeywordUrl($url, $replace) {
  $count = 0;
  $url = str_replace('%s', $replace, $url, $count);
  if ($count == 1) {
    header('HTTP/1.0 301 Relocating');
    header("Location: " . $url);
    return TRUE;
  }
  return FALSE;
}

/** Add a url to snippy db */
function addurl() {
  global $settings;

  $kw = $_REQUEST['kw'];
  if ($kw) {
    // work out the values from the kw param
    $p = explode(' ', $kw, 2);
    if (count($p) == 2) {
      $n = $p[0];
      $u = $p[1];
    } else {
      $u = $p[0];
    }
  } else {
    $u = $_REQUEST['u'];
    $n = $_REQUEST['n'];
  }

  if ($u) {
    if (strpos($u, 'http') !== 0) {
      doNothing("not found '$u'");
      return false;
    }
    checkPermissions();
    loadConfig();

    foreach ($settings['urls'] as $kw => $url) {
      if ($url == $u) {
        redirectToShowUrl("URL already found", $kw);
        return true;
      }
    }
    if (isset($settings['urls'][$n])) {
      //  perhaps they're using keyvoard replacement?
      $url = $settings['urls'][$n];
      if (strpos($url, '%s') !== FALSE) {
        redirectToKeywordUrl($url, $u);
        return;
      } else {
        doNothing("Something already exists with keyword $n");
      }
    }

    if (!$n) {
      $n = getNextID();
      redirectToShowUrl("Created random url", $n);
    } else {
      // clean up $n
      $n = cleanName($n);
      redirectToShowUrl("Created url", $n);
    }

    appendLink($n, $u);

    return true;
  }
  return false;
}

/** make sure name is clean. Clean means only letters, numbers '_' and '-' */
function cleanName($n) {
  return preg_replace('/[^a-z0-9_-]/i', '', $n);
}

/** send them to a page that will show them the url created.
 * We do this to avoid disclosing the add url in the referrer */
function redirectToShowUrl($txt, $n) {
  $value = sprintf("%s?show=%s&txt=%s",
                   BASEURL, urlencode($txt), urlencode($n));
  header('HTTP/1.0 301 Relocating');
  header("Location: " . $value);
}

/** Show a page with a form for them to add a snippy url.
 * This is currently not used. */
function showAddAPage() {
?>

<html><head><title>Add a Page</title>
</head><body>
<h1 align=center><u>Add a Page</u></h1>

<form method=GET action="add.php">
<table>
<tr><td>URL:</td><td><input type=text name=u size=80
  value="<?php echo $_REQUEST['u'] ?>"></td></tr>
<tr><td>Name:</td><td><input type=text name=n
value="<?php echo $_REQUEST['n'] ?>"></td></tr>
<tr><th colspan=2><input type=submit name=s value='go'></th></tr>

</table>
</form>
</body></html>
<?php
}

/** Show the user the newly created snippy url. */
function showUrl($txt, $n) {
  $url = BASEURL . $n;
?>
<form name="text" method="post" action="<?php echo $url ?>">
<?php echo $txt ?> at
<input name="tiny" type="text" value="<?php echo $url ?>">
</form>
<script type="text/javascript">
  document.forms.text.tiny.focus();
  document.forms.text.tiny.select();
</script>
<?php
}


/** Take a json element and if it's a bookmark work out if we want to add it.
 * Otherwise recurse down the children of this json element.
 */
function findUnusedKeywordsInBookmarks($elem, &$toadd) {
  global $settings;
  if (isset($elem->keyword)) {
    if (!isset($settings['urls'][$elem->keyword])) {
      printf("<tr><th>%s</th><td>%s</td></tr>\n", $elem->keyword, $elem->uri);
      $toadd[] = sprintf("%s=\"%s\"", $elem->keyword, $elem->uri);
    }
  }

  if (isset($elem->children)) {
    foreach ($elem->children as $e) {
      findUnusedKeywordsInBookmarks($e, $toadd);
    }
  }
}


/** Take a bookmarks json file and find all bookmakrs with keywords
 * and add any unknown keywords.
 */
function ProcessBookmarksFile($filename) {
  global $settings;

  $bm = json_decode(file_get_contents($filename));

  $toadd = array();

  print "<table>";
  findUnusedKeywordsInBookmarks($bm, $toadd);
  print "</table>";

  if ($toadd) {
    print "Adding to database\n";
    $fh = fopen(CONFIGFILE, 'a');
    fwrite($fh, implode("\n", $toadd));
    fwrite($fh, "\n");
    fclose($fh);
  } else {
    print "No new keywords to add\n";
  }
}

/** Show a form where people can upload a bookmarks json file */
function ShowUploadBookmarksForm() {
  ?>
<form enctype="multipart/form-data" method=POST action="<?php echo BASEURL ?>">
<input type="hidden" name="MAX_FILE_SIZE" value="300000" />
   Send this file: <input name="bookmarks" type="file" />
<input type="submit" value="Send File" />
</form>
<?php
}


/** Handle the request, dispatches to the various other methods which
 * may just show the url, add a url, or even lookup a snippy url.
 * returns true if an action was taken, false otherwise.
 */
function main() {
  global $settings;
  if (isset($_REQUEST['show'])) {
    showUrl($_REQUEST['show'], $_REQUEST['txt']);
    return;
  }

  if (isset($_REQUEST['kw'])) {
    # First look up to see if 'kw' is in the directory if it is just load it.
    # This allows you to use your mozilla keyword to use as well as add things.
    loadConfig();
    if (isset($settings['urls'][$_REQUEST['kw']])) {
      header('HTTP/1.0 301 Relocating');
      header("Location: " . $settings['urls'][$_REQUEST['kw']]);
      return;
    }
    addUrl($_REQUEST['kw']);
    return;
  }

  if (isset($_REQUEST['upload'])) {
    checkPermissions();
    ShowUploadBookmarksForm();
    return;
  }

  if (isset($_FILES['bookmarks'])) {
    checkPermissions();
    ProcessBookmarksFile($_FILES['bookmarks']['tmp_name']);
    return;
  }

  $n = $_SERVER['REQUEST_URI'];

  if ($n) {
    # strip off the leading /
    $n = substr($n, strlen(BASEPATH));
  }

  if ($n) {
    loadConfig();
    # check for an instant match
    if (isset($settings['urls'][$n])) {
      header('HTTP/1.0 301 Relocating');
      header("Location: " . $settings['urls'][$n]);
      return;
    }
    # perhaps this is parameterized so we need to look for space
    # e.g. you have gg set to be http://www.google.com/search?q=%s
    # you can now do /gg fish
    # and it will search for fish!
    $p = explode(' ', urldecode($n), 2);
    if (count($p) == 2) {
      $n = $p[0];
      if (isset($settings['urls'][$n])) {
        $u = urlencode($p[1]);
        $n = $settings['urls'][$n];
        if (redirectToKeywordUrl($n, $u)) {
          return;
        }
      }
    }

  }
  doNothing('');
}

/** does nothing, prints out a regular page with a message and exits */
function doNothing($msg) {
  header('HTTP/1.0 200 Not Found');
  if (is_readable(FAILOVERFILE)) {
    include FAILOVERFILE;
  } else {
    defaultPage($msg);
  }
  exit();
}

function defaultPage($msg) {
  ?>
  <html><head><title><?php echo $_SERVER{'SERVER_NAME'} ?></title></head><body>
  <h1><?php echo $_SERVER{'SERVER_NAME'} ?></h1><?= $msg ?></body></html>
  <?php
}

/** run the main method. */
main();
?>
